﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Leaves = System.Collections.Generic.List<WPFTrees.DataStructures.NTreeNode<WPFTrees.Parser.NewickNode>>;
using System.Windows.Media;
using System.Windows.Controls;
using System.Diagnostics;

namespace WPFTrees.LabelOptimization
{
    public class AnnealingSettings
    {
        public AnnealingSettings()
        {

        }

        public AnnealingSettings(double alpha, double temperature,
            double epsilon, double rotationAngle)
        {
            this.Alpha = alpha;
            this.Temperature = temperature;
            this.Epsilon = epsilon;
            this.RotationAngle = rotationAngle;

        }
        public double Alpha = 0.999; // rychlost chlazeni
        public double Temperature = 1000.0; // pocatecni teplota
        public double Epsilon = 0.001; // cilova teplota
        public double RotationAngle = 180;

    }

    class LabelOptimizer
    {       
        internal static void Optimize(Leaves leaves, Canvas canvas, AnnealingSettings settings)
        {
            // simulated annealing
            
            // 1.
            double temperature = settings.Temperature;
            
            double delta, probability;
            // skore = pocet prekrizeni popisku
            double currentScore = ValueFunction(leaves,canvas), newScore;
            int currentLeaf = 0;
            Transform current = GetRotationTransform(leaves,currentLeaf);
            Transform previous = current;
            Random rnd = new Random();

            double start = currentScore;
            int iteration = 0;
            Stopwatch watch = new Stopwatch();
            watch.Start();
            // dokud nejsem spokojenej s vysledkem tak iteruju
            while (temperature > settings.Epsilon)
            {
                #region loop                
                if (currentScore == 0)
                {
                    MessageBox.Show(string.Format("Started with {0} overlapping. Now has {1} overlapping. It took {2} s and {3} iterations.",
                start, currentScore, watch.Elapsed.TotalSeconds, iteration));
                    return; // pocatecni reseni vyhovuje, tak skoncim :)
                }
                // zkusim novou iteraci
                
                int leaf = rnd.Next(leaves.Count);

                previous = leaves[leaf].Value().VisualElement.LayoutTransform;
                RotateLabel(leaves,leaf, settings.RotationAngle);
                // jak se zmenilo skore?
                newScore = ValueFunction(leaves,canvas);
                delta = currentScore - newScore;
                
                if (currentScore > newScore) // mensi je lepsi
                {
                    Debug.Print(string.Format("{0}->{1}", currentScore, newScore));

                    // nasel sem lepsi reseni
                    currentScore = newScore;
                    previous = current;
                }
                else
                { 
                    // nasel sem horsi reseni, vracim se k starymu
                    SetRotationTransform(leaves, previous, leaf);
                }

                //if (delta < 0.0)
                //{
                //    // skore se zhorsilo, prijmi konfiguraci s pravdepodobnosti
                //    probability = rnd.NextDouble();
                //    /*if (probability < Math.Exp(-delta / temperature))
                //    {
                //        currentScore = newScore;
                //        current = next;
                //    }
                //    else*/
                //    { 
                //        // jinak se vracim k puvodni
                //        SetRotationTransform(leaves, current,currentLeaf);
                //    }

                //}
                //else
                //{ 
                //    // prijmi novou konfiguraci
                //    // takze nic nedelam, jenom
                //    currentScore = newScore;
                //    current = next;
                //}

                iteration++;
                temperature *= settings.Alpha;
                if (iteration % 100 == 0)
                {
                    Debug.Print(temperature.ToString());
                    Debug.Print(currentScore.ToString());

                    MainWindow window = (Application.Current.MainWindow as MainWindow);
                    //window.RenderTree();
                }
                #endregion
            }
            watch.Stop();
            MessageBox.Show(string.Format("Started with {0} overlapping. Now has {1} overlapping. It took {2} s and {3} iterations.",
                start,currentScore,watch.Elapsed.TotalSeconds,iteration));
        }

        #region http://stackoverflow.com/questions/597764/wpf-collision-detection-with-rotated-squares
        
        //TODO: upravit na moje struktury
        public static Rect GetBounds(FrameworkElement of, FrameworkElement canvas)
        {
            // Might throw an exception if of and from are not in the same visual tree
            GeneralTransform transform = of.TransformToVisual(canvas);

            return transform.TransformBounds(new Rect(0, 0, of.ActualWidth, of.ActualHeight));
        }

        //TODO: jak presne pocitat kolize? tohle znamena true kdyz se krizi s libovolnym >0 poctem
        // mozna bych spis mel v tom cyklu pripocitavat 1 a vracet pocet krizeni s danym labelem?
        static bool IsLabelCollided(FrameworkElement canvas, FrameworkElement label, Leaves leaves)
        {
            
            //currentBounds is of type Rect, which contains the 4 points of the rectangle (even when rotated)
            var currentLabel = label.Tag as Label;
            var currentBounds = GetBounds(currentLabel, canvas);
            
            //Then I check if the current bounds intersect with each of the other Bots` bounds that are also in the Arena
            foreach (var item in leaves)
            {
                
                var otherElement =  item.Value().VisualElement;
                var otherLabel = otherElement.Tag as Label;
                if (otherLabel == currentLabel) continue;
                Rect otherLabelBounds = GetBounds(otherLabel, canvas);
                if (otherLabelBounds.IntersectsWith(currentBounds))
                {
                    //Debug.Print(otherLabel.Content.ToString() + ":" + otherLabelBounds.ToString());
                    //Debug.Print(currentLabel.Content.ToString() + ":" + currentBounds.ToString());
                    //Debug.WriteLine("");

                    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!
                    //TODO: tohle pocitani evidentne nefunguje!!
                    // zkontroluj souradnice atd...
                    //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
                    //ne.nezkompiluje.se.to.dokud.to.neopravis!
                    return true;
                }
            }
            return false;
        }

        #endregion

        private static Transform GetRotationTransform(Leaves leaves, int leaf)
        {            
            return leaves[leaf].Value().VisualElement.LayoutTransform;
        }

        private static void SetRotationTransform(Leaves leaves, Transform transform, int leaf)
        {            
            leaves[leaf].Value().VisualElement.LayoutTransform = transform;            
        }

        static int ValueFunction(Leaves leaves, Canvas canvas)
        {
            //TODO: return number of overlapping
            int overlappings = 0;
            foreach (var item in leaves)
            {
                if(IsLabelCollided(canvas,item.Value().VisualElement,leaves))
                {
                    overlappings++;
                }
            }
            return overlappings;
        }

        /// <summary>
        /// Pick a label, and rotate it
        /// </summary>
        /// <param name="leaves"></param>
        static void RotateLabel(Leaves leaves, int leaf, double rotationAngle)
        {
            Random rnd = new Random();
            

            var item = leaves[leaf];
            FrameworkElement element = item.Value().VisualElement;
            double angle = rotationAngle*rnd.NextDouble(); /* uhel otoceni 0-10 stupnu*/
            double x,y;
                
            x = Canvas.GetLeft(element);
            y = Canvas.GetTop(element);
            //Point pos =new Point(x,y);
            RotateTransform transform = new RotateTransform(angle, x, y);

            TransformGroup group = new TransformGroup();
            group.Children.Add(transform);
            if(element.LayoutTransform!=null) // ??
            group.Children.Add(element.LayoutTransform);
                
            element.LayoutTransform = group;                            

            (Application.Current.MainWindow as MainWindow).miTree.UpdateLayout();
        }
    }
}
